/*
 * Copyright (c) 2023, Phytium Technology Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <boost/system/error_code.hpp>
#include <ipmiblob/crc.hpp>
#include <ipmid/api-types.hpp>
#include <ipmid/api.hpp>
#include <ipmid/handler.hpp>
#include <phosphor-logging/log.hpp>

#include <filesystem>
#include <fstream>
#include <memory>

namespace oem
{
constexpr uint8_t cmdDevicesInformation = 0x40;
} // namespace oem

std::vector<uint8_t> devcies(0);

ipmi::RspType<std::vector<uint8_t>>
    ipmiDevicesInformation(const std::vector<uint8_t>& reqData)
{
    const uint8_t devIndex = reqData.front();

    if (!devIndex)
    {
        devcies.resize(0);
    }

    const size_t devSize = devcies.size();
    const size_t reqDevSize = reqData.size() - 1;

    devcies.resize(devSize + reqDevSize);
    std::memcpy(devcies.data() + devSize, reqData.data() + 1, reqDevSize);

    if (devIndex == 0xff)
    {
        const uint16_t crc16 = static_cast<uint16_t>(
            (devcies.back() << 8) | *std::prev(devcies.end(), 2));

        /* Delete the last two elements. */
        devcies.pop_back();
        devcies.pop_back();

        if (crc16 != ipmiblob::generateCrc(devcies))
        {
            phosphor::logging::log<phosphor::logging::level::ERR>(
                "CRC16 verification error.");
            devcies.resize(0);
            return ipmi::responseInvalidFieldRequest();
        }

        static const char* devPath = "/var/lib/devicemanager/";

        if (!std::filesystem::exists(devPath))
        {
            std::filesystem::create_directories(devPath);
        }

        std::fstream devStream(devPath + std::string("devices.bin"),
                               std::ios::trunc | std::ios::out |
                                   std::ios::binary);
        if (!devStream.is_open())
        {
            phosphor::logging::log<phosphor::logging::level::ERR>(
                "Failed to open file.");
            devcies.resize(0);
            return ipmi::responseUnspecifiedError();
        }
        devStream.write(reinterpret_cast<char*>(devcies.data()),
                        devcies.size());
        devStream.close();

        try
        {
            /*
             * Notify the backend process to scan and process the
             * saved binary files, and ultimately display the data
             * in DBus format.
             */
            sdbusplus::message::message msg = getSdBus()->new_signal(
                "/xyz/openbmc_project/inventory",
                "xyz.openbmc_project.Device.Manager", "ScanDevice");
            msg.signal_send();
        }
        catch (...)
        {
            phosphor::logging::log<phosphor::logging::level::ERR>(
                "Message signal sending failed.");
        }
        devcies.resize(0);
    }
    return ipmi::responseSuccess();
}

static void register_fru_functions() __attribute__((constructor));
static void register_fru_functions()
{
    ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnOemSix,
                          oem::cmdDevicesInformation, ipmi::Privilege::User,
                          ipmiDevicesInformation);
    return;
}
